相信很多人都有寫過組合語言,但應該沒想過可以在 C 語言中也撰寫組合語言吧,總之這是辦得到的,那麼以下會針對這相關的語法作一些解釋,並不會去詳細介紹每種組語的語法,會以 x86/x64 為主,並且介紹其中與 Synchronization 較相關的部份
參考 Is there a way to insert assembly code into C?
__asm__("movl %edx, %eax\n\t"
"addl $2, %eax\n\t")
asm("" ::: "memory")
__asm {
mov eax, edx
add eax, 2
}
不同編譯器,使用的語法有些微差異
那麼來講述一個 asm
的語法該如何閱讀,參考以下程式碼,參數使用冒號作為分割符
asm ( assembler template
: output operands (optional)
: input operands (optional)
: clobbered registers list (optional)
);
#include <stdio.h>
int main() {
int a=10, b, c;
asm("movl %1, %0\n\t"
:"=r"(b) /* output */
:"r"(a) /* input */
);
printf("a = %d, b = %d\n", a, b);
return 0;
}
=r(b)
,=
表示 write-only,r
表示 general register,(b)
則是指變數 b
%0
表示 output operand%1
表示 input operand=m(b)
除了存放在 register ,也可以放在記憶體"0"(a)
如果指定數字代表與第幾個 operand 共用 register參考 What is the correct use of multiple input and output operands in extended GCC asm?
#include <stdio.h>
int main() {
int input0 = 10;
int input1 = 15;
int output0 = 0;
int output1 = 1;
asm volatile("mov %[input0], %[output0]\t\n"
"mov %[input1], %[output1]\t\n"
: [output0] "=r" (output0), [output1] "=r" (output1)
: [input0] "r" (input0), [input1] "r" (input1)
:);
printf("output0: %d\n", output0);
printf("output1: %d\n", output1);
return 0;
}
可以使用 [output0]
這樣的語法,幫變數命名
若成功執行會發現結果會是錯誤的,output0
, output1
都被修改成 10,因為 GCC 認為只有在最後才會去對 output operand 作寫入的動作,所以他們可能共用同一個 register,這樣的話會導致錯誤
volatile
也可以搭配 asm 作使用,反正就是用來阻止編譯器幫你優化
正確版本
asm volatile("mov %[input0], %[output0]\t\n"
"mov %[input1], %[output1]\t\n"
: [output0] "=&r" (output0), [output1] "=r" (output1)
: [input0] "r" (input0), [input1] "r" (input1)
:);
我們在 [output0] "=&r" (output0)
這個地方新增了 &
,這是一個 early clobber 的符號,表示該 operand 在執行 instruction 讀取之前就會先被寫入
參考 When to use earlyclobber constraint in extended GCC inline assembly?
#include <stdio.h>
int main(void) {
int in = 1;
int out;
asm (
"mov %[in], %[out];" /* out = in */
"inc %[out];" /* out++ */
"mov %[in], %[out];" /* out = in */
"inc %[out];" /* out++ */
: [out] "=&r" (out)
: [in] "r" (in)
:
);
printf("in = %d, out = %d\n", in, out);
}
#include <stdio.h>
int main() {
int EAX;
asm( "movl $0, %0"
:"=a" (EAX)
);
return 0;
}
a
代表 register eax,希望 int EAX 這個變數,多被放在 register 的意思,b
, c
, d
等也都有對應的 register ,這可以自行參閱手冊前一天所提到的 CAS ,這其實早就有硬體實作的版本了,這是一小段在 Kernel 中會使用到的程式碼
#define __raw_cmpxchg(ptr, old, new, size, lock) \
({ \
__typeof__(*(ptr)) __ret; \
__typeof__(*(ptr)) __old = (old); \
__typeof__(*(ptr)) __new = (new); \
\
volatile u32 *__ptr = (volatile u32 *)(ptr); \
asm volatile(lock "cmpxchgl %2,%1" \
: "=a" (__ret), "+m" (*__ptr) \
: "r" (__new), "0" (__old) \
: "memory"); \
\
__ret; \
})
lock
並不是一個 instruction,他是一個 instruction prefix,用來告訴 CPU 在執行接下來的 instruction 時,要注意什麼,lock
指的是要鎖住 bus,想知道更多的 instruction prefix 可以參閱 這份資料 21.5. Instruction Prefixes
__ret
與 __old
共用同一個 register eaxmemory
代表 compiler barrier,稍後會解釋cmpxchgl
就是 CAS 的指令,看以下 C 語言程式碼來解釋,其中的 ZF
我搞不太清楚在幹麻// cmpxchgl __new, *__ptr
// cmpxchgl ebx, ecx
if(eax == ebx) {
ebx = ecx
ZF = 1
} else {// 舊的值被改動過,只好塞新的值進去
eax = ebx
ZF = 0;
}
在昨天又或者是前一個範例,我們都有提到 memory barrier,但究竟什麼是 memory barrier?要理解memory barrier 就必須知道這不僅僅是語法的學習,要對 cpu 是如何執行以及自己的程式碼邏輯有非常深刻的了解
我們可以從 memory order 開始了解,memory order 指的是 cpu 從記憶體抓資料的順序,現代處理器通常都是亂序執行,也就是沒有什麼硬性的規定,但大家可能會很疑惑,他既然是亂序執行,那程式執行結果不就完全亂掉了嗎?對,會亂掉。但亂掉只會在多核平行化的程式產生問題,因為亂序執行也是有遵循一些規則,並不是單純的亂序
遵循什麼規則?遵循 as-if-serial
的限制,這保證了 single thread 的程式,在執行時的正確性
比如以下程式, a = 10
b = 5
誰先執行都不重要,只要在 c = a*b
之前執行即可
#include <stdio.h>
int main() {
int a, b, c;
a = 10;// write
b = 5;// write
c = a*b;
return 0;
}
那麼什麼時候會發生亂序執行呢?這其實只有在 lock-free 相關的程式會發生,如果每個要修改的變數,在改動前都有作 lock,是不會發生這種事情的,但這麼作的話在很多情況是很沒效率的
到目前為止可以發現,影響程式執行正確性的點在於「什麼時候寫入/載入」,也就是「load/store 的順序」,到這個部份就明天再說,因為我還在研讀資料 ......
正在閱讀
%0 表示 output operand
%1 表示 input operand
這句話是誤導,沒有人規定 %0
就一定是 output operand,不然的話 %2
, %3
怎麼解釋?
From: https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
When not using an asmSymbolicName, use the (zero-based) position of the operand in the list of operands in the assembler template. For example if there are three output operands, use ‘%0’ in the template to refer to the first, ‘%1’ for the second, and ‘%2’ for the third.